use super::{adv_cfg, cargo, Conn};
use crate::{
    error::{ec, new_err_msg, AppErr, SqlxErr},
    store::get_pool,
    utils::current_timestamp,
};
use serde::Serialize;

#[derive(Debug, Serialize)]
pub struct TbDevice {
    pub id: u64,
    name: Option<String>,
    language: Option<String>,
    location: Option<String>,
    mac_addr: String,
    ipc_version: Option<String>,
    phone_number: Option<String>,
    mcu_version: Option<String>,
    tick_timestamp: u64,
}

async fn is_exists(conn: &mut Conn, mac_addr: &str) -> Result<Option<u64>, SqlxErr> {
    let ret = sqlx::query!("SELECT id FROM tb_device WHERE mac_addr = ?", mac_addr)
        .fetch_optional(conn)
        .await?;
    Ok(ret.and_then(|r| Some(r.id)))
}

async fn create_impl(conn: &mut Conn, mac_addr: &str, ipc_version: &str) -> Result<u64, SqlxErr> {
    let ret = sqlx::query!(
        r#"
        INSERT INTO tb_device (mac_addr, ipc_version, tick_timestamp) 
        VALUES (?, ?, ?)"#,
        mac_addr,
        ipc_version,
        current_timestamp()
    )
    .execute(&mut *conn)
    .await?;
    let device_id = ret.last_insert_id();
    cargo::create(conn, device_id).await?;
    Ok(device_id)
}

pub async fn login(mac_addr: &str, ipc_version: &str) -> Result<u64, AppErr> {
    let mut tx = get_pool().begin().await?;

    if let Some(id) = is_exists(&mut tx, mac_addr).await? {
        sqlx::query!(
            "UPDATE tb_device SET ipc_version = ? WHERE id = ?",
            ipc_version,
            id
        )
        .execute(&mut *tx)
        .await?;
        tx.commit().await?;
        return Ok(id);
    }

    match create_impl(&mut tx, mac_addr, ipc_version).await {
        Ok(id) => {
            tx.commit().await?;
            Ok(id)
        }
        Err(SqlxErr::Database(err)) if err.is_unique_violation() => {
            Err(new_err_msg(ec::DEVICE_EXISTS, ec::DEVICE_EXISTS_MSG))
        }
        Err(e) => Err(e.into()),
    }
}

pub async fn update_with_client(
    id: u64,
    ipc_version: &str,
    phone_number: Option<&str>,
    mcu_version: &str,
) -> Result<(), SqlxErr> {
    sqlx::query!(
        r#"
        UPDATE tb_device SET ipc_version = ?, phone_number = ?, mcu_version = ? 
        WHERE id = ?
    "#,
        ipc_version,
        phone_number,
        mcu_version,
        id
    )
    .execute(get_pool())
    .await?;

    Ok(())
}

pub async fn update_with_web(
    id: u64,
    name: Option<&str>,
    language: Option<&str>,
    location: Option<&str>,
) -> Result<(), SqlxErr> {
    sqlx::query!(
        r#"
        UPDATE tb_device SET name = ?, language = ?, location = ? WHERE id = ?
        "#,
        name,
        language,
        location,
        id
    )
    .execute(get_pool())
    .await?;

    Ok(())
}

pub async fn update_tick(id: u64) -> Result<(), SqlxErr> {
    sqlx::query!(
        r#"UPDATE tb_device SET tick_timestamp = ? WHERE id = ?"#,
        current_timestamp(),
        id
    )
    .execute(get_pool())
    .await?;
    Ok(())
}

pub async fn query_all() -> Result<Box<[TbDevice]>, SqlxErr> {
    let ds = sqlx::query_as!(
        TbDevice,
        "SELECT id, name, language, location, mac_addr, ipc_version, phone_number, mcu_version, tick_timestamp FROM tb_device"
    ).fetch_all(get_pool()).await?;
    Ok(ds.into_boxed_slice())
}

pub async fn get(id: u64) -> Result<TbDevice, SqlxErr> {
    sqlx::query_as!(
        TbDevice,
        "SELECT id, name, language, location, mac_addr, ipc_version, phone_number, mcu_version, tick_timestamp FROM tb_device WHERE id = ? LIMIT 1",
        id
    ).fetch_one(get_pool()).await
}

pub async fn delete(id: u64) -> Result<(), SqlxErr> {
    let mut tx = get_pool().begin().await?;
    sqlx::query!("DELETE FROM tb_device WHERE id = ?", id)
        .execute(&mut *tx)
        .await?;
    cargo::delete(&mut tx, id).await?;
    adv_cfg::delete_for_device(&mut tx, id).await?;
    tx.commit().await?;
    Ok(())
}
